package com.ncu.spring.aop.advisor;

import com.ncu.spring.aop.AspectInstanceFactory;
import com.ncu.spring.aop.advisor.joinpoint.ProceedingJoinPoint;
import com.ncu.spring.core.Ordered;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 *  就是所有Advice都要的操作
 */
public abstract class CommonAdvice implements Advice, Ordered {
    //切面方法
    private Method aspectJAdviceMethod;
    //调用切面方法的类的对象
    private AspectInstanceFactory aspectInstanceFactory;
    //默认拦截所有的异常类型
    private Class<?> discoveredThrowingType = Object.class;

    /**
     * @AfterThrowing( value = "execution(* foo())", throwing = "java.lang.Exception")
     * throwing 的值
     */
    private String throwingName;

    public CommonAdvice(Method aspectJAdviceMethod, AspectInstanceFactory aspectInstanceFactory) {
        aspectJAdviceMethod.setAccessible(true);  //设置这个方法支持反射使用私有方法
        this.aspectJAdviceMethod = aspectJAdviceMethod;
        this.aspectInstanceFactory = aspectInstanceFactory;
    }


    /**
     * 调用通知方法，完成简单的参数回显解析 :将通知方法中的参数进行设置
     */
    protected Object invokeAdviceMethod(ProceedingJoinPoint pjp, Throwable ex) throws Throwable {
        // 准备方法参数
        int parameterCount = this.aspectJAdviceMethod.getParameterCount();
        Object[] args = new Object[parameterCount];

        // 存在异常，@AfterThrowing 通知的调用
        if (ex != null) {
            // 设置了 throwingName，但没有通知方法中没有参数，报错
            // 例：对应默认的 @AfterThrowing ，注解中不设置 throwing
            //@AfterThrowing( value = "execution(* foo())", throwing = "java.lang.ClassNotFoundException")
            //public void afterThrowing(){}  //就像这样会报错 因为@AfterThrowing通知方法因为需要设置异常参数 如：
            //public void afterThrowing(Exception e){}
            if (parameterCount == 0) {
                if (this.throwingName != null) {
                    throw new IllegalStateException(this.throwingName+" 设置了 Throwing但是通知方法上没有设置参数");
                }
            } else {
                //通知方法如果有参数的话，第一个参数必须是异常类型
                args[0] = ex;
            }
        }

        // 存在 ProceedingJoinPoint，@Around 通知的调用 与上面同理 如：
        //@Around(value = "execution(* foo())")
        //public void around(){}  错 因为需要一个参数来代表当前的MethodInvocation
        //public void around(ProceedingJoinPoint point)  对
        if (pjp != null) {
            if (parameterCount == 0) {
                throw new IllegalStateException("环绕通知的参数中缺少 ProceedingJoinPoint");
            } else {
                args[0] = pjp;
            }
        }
        return invokeAdviceMethod(args);
    }

    /**
     * AfterThrowing 异常回显
     * 环绕通知会用到通知方法的返回值，其他通知用不到
     */
    private Object invokeAdviceMethod(Object[] args) throws Throwable {
        try {
            //将这个工厂类的对象 反射调用一遍 发生异常就向上抛出
            //没有异常 就将结果 返回给 环绕通知
            return this.aspectJAdviceMethod.invoke(this.aspectInstanceFactory.getAspectInstance(), args);
        } catch (IllegalAccessException e) {
            throw e;
        } catch (InvocationTargetException e) {
            throw e.getTargetException();
        }
    }
    //设置 拦截的异常
    // 规定如果 @AfterThrowing 的 throwing 参数设置为 ex，那么参数列表第 0 个必须是异常类
    //Throwing 如果是"ex" 表示想用名字进行匹配 那么切面方法的第一个参数就是需要拦截的异常类
    //Throwing 如果不是"ex" 表示想用类型匹配 那么Throwing中的字符串 就是需要拦截的异常类的全类名
    protected void setThrowingName(String name) {
        if (name.equals("ex")) {
            this.throwingName = name;
            // 下面的方法不行，因为编译后的 class 没有参数名信息，变成 arg0、arg1 这种
            /*Parameter[] parameters = this.aspectJAdviceMethod.getParameters();
            for (Parameter parameter : parameters) {
                // 找到要拦截的异常类型
                if (parameter.getName().equals(this.throwingName)) {
                    this.discoveredThrowingType = parameter.getType();
                    return;
                }
            }*/
            Class<?> exClass = null;
            try {
                //获取切面方法的第一个参数 ：是异常类（规定）
                exClass = this.aspectJAdviceMethod.getParameterTypes()[0];
            } catch (ArrayIndexOutOfBoundsException e) {
                throw new IllegalArgumentException("方法中缺少异常参数。method = " + this.aspectJAdviceMethod);
            }
            //判断exClass 是否是Throwable的子类
            if (Throwable.class.isAssignableFrom(exClass)) {
                this.discoveredThrowingType = exClass; //是将拦截的异常设置为该类
            } else {
                throw new IllegalArgumentException("方法中缺少异常参数，找不到要拦截的异常类型。method = " + this.aspectJAdviceMethod);
            }
        } else if (name.length() > 0) { //如果Throwing 不是ex 就是用类型匹配
            this.throwingName = name;
            try {
                //通过全限定名称 获取该异常的Class对象
                this.discoveredThrowingType = Class.forName(name);
            } catch (Throwable ex) {
                throw new IllegalArgumentException(name +" 这个全限定名称找不到对应的类");
            }
        } else {
            // throwing 没提供，默认拦截索引异常
        }
    }
    //获取拦截的异常类型
    protected Class<?> getDiscoveredThrowingType() {
        return this.discoveredThrowingType;
    }

}
